home *** CD-ROM | disk | FTP | other *** search
- # -*- coding: utf-8 -*-
- """
- """
-
- __author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
- __license__ = 'GNU General Public License Version 2'
- __copyright__ = 'Copyright 2005-2006 Robert Ancell'
-
- import chess.board
- import chess.san
-
- # Game results
- RESULT_IN_PROGRESS = '*'
- RESULT_WHITE_WINS = '1-0'
- RESULT_BLACK_WINS = '0-1'
- RESULT_DRAW = '1/2-1/2'
-
- # Reasons for the result
- RULE_CHECKMATE = 'CHECKMATE'
- RULE_STALEMATE = 'STALEMATE'
- RULE_TIMEOUT = 'TIMEOUT'
- RULE_FIFTY_MOVES = 'FIFTY_MOVES'
- RULE_THREE_FOLD_REPETITION = 'THREE_FOLD_REPETITION'
- RULE_INSUFFICIENT_MATERIAL = 'INSUFFICIENT_MATERIAL'
- RULE_RESIGN = 'RESIGN'
- RULE_DEATH = 'DEATH'
- RULE_AGREEMENT = 'AGREEMENT'
- RULE_ABANDONMENT = 'ABANDONMENT'
-
- class ChessMove:
- """
- """
-
- # The move number (game starts at 0)
- number = 0
-
- # The player and piece that moved
- player = None
- piece = None
-
- # The piece that was promoted to (or None)
- promotion = None
-
- # The victim piece (or None)
- victim = None
-
- # The start and end position of the move
- start = None
- end = None
-
- # The move in CAN and SAN format
- canMove = ''
- sanMove = ''
-
- # The game result after this move
- opponentInCheck = False
- opponentCanMove = False
-
- # If this move can be used as a resignation
- fiftyMoveRule = False
- threeFoldRepetition = False
-
- # A comment about this move
- comment = ''
-
- # Numeric annotation glyph for move
- nag = ''
-
- class ChessPlayer:
- """
- """
-
- def __init__(self, name):
- """Constructor for a chess player.
-
- 'name' is the name of the player.
- """
- self.__name = str(name)
- self.__game = None
- self.__readyToMove = False
- self.isAlive = True
-
- # Methods to extend
-
- def onPieceMoved(self, piece, start, end, delete):
- """Called when a chess piece is moved.
-
- 'piece' is the piece that has been moved (chess.board.ChessPiece).
- 'start' is the location the piece in LAN format (string) or None if the piece has been created.
- 'end' is the location the piece has moved to in LAN format (string).
- 'delete' is a flag to show if the piece should be deleted when it arrives there (boolean).
- """
- pass
-
- def onPlayerMoved(self, player, move):
- """Called when a player has moved.
-
- 'player' is the player that has moved (ChessPlayer).
- 'move' is the record for this move (ChessMove).
- """
- pass
-
- def onUndoMove(self):
- pass
-
- def onPlayerStartTurn(self, player):
- pass
-
- def onGameEnded(self, game):
- """Called when a chess game has ended.
-
- 'game' is the game that has ended (Game).
- """
- pass
-
- def readyToMove(self):
- """FIXME
- """
- pass
-
- # Public methods
-
- def getName(self):
- """Get the name of this player.
-
- Returns the player name (string).
- """
- return self.__name
-
- def getGame(self):
- """Get the game this player is in.
-
- Returns the game (Game) or None if not in a game.
- """
- return self.__game
-
- def getRemainingTime(self):
- """Get the amount of time this player has remaining.
-
- Returns the amount of time in milliseconds.
- """
- if self is self.__game.getWhite():
- timer = self.__game.whiteTimer
- elif self is self.__game.getBlack():
- timer = self.__game.blackTimer
- else:
- return 0
-
- if timer is None:
- return 0
- else:
- return timer.controller.getRemaining()
-
- def isReadyToMove(self):
- """
- """
- return self.__readyToMove
-
- def canMove(self, start, end, promotionType = chess.board.QUEEN):
- """
- """
- return self.__game.canMove(self, start, end, promotionType)
-
- def move(self, move):
- """Move a piece.
-
- 'move' is the move to make in Normal/Long/Standard Algebraic format (string).
- """
- self.__game.move(self, move)
-
- def undo(self):
- """Undo moves until it is this players turn"""
- self.__game.undo(self)
-
- def endMove(self):
- """Complete this players turn"""
- self.__game.endMove(self)
-
- def resign(self):
- """Resign from the game"""
- self.__game.resign(self)
-
- def claimDraw(self):
- """Claim a draw"""
- return self.__game.claimDraw()
-
- def outOfTime(self):
- """Report this players timer has expired"""
- self.__game.outOfTime(self)
-
- def die(self):
- """Report this player has died"""
- self.isAlive = False
- if self.__game is not None:
- self.__game.killPlayer(self)
-
- # Private methods
-
- def _setGame(self, game):
- """
- """
- self.__game = game
-
- def _setReadyToMove(self, readyToMove):
- if self.__readyToMove == readyToMove:
- return
- self.__readyToMove = readyToMove
- if readyToMove is True:
- self.readyToMove()
-
- class ChessGameBoard(chess.board.ChessBoard):
- """
- """
-
- def __init__(self, game):
- """
- """
- self.__game = game
- chess.board.ChessBoard.__init__(self)
-
- def onPieceMoved(self, piece, start, end, delete):
- """Called by chess.board.ChessBoard"""
- self.__game._onPieceMoved(piece, start, end, delete)
-
- class ChessGameSANConverter(chess.san.SANConverter):
- """
- """
-
- __colourToSAN = {chess.board.WHITE: chess.san.SANConverter.WHITE,
- chess.board.BLACK: chess.san.SANConverter.BLACK}
- __sanToColour = {}
- for (a, b) in __colourToSAN.iteritems():
- __sanToColour[b] = a
-
- __typeToSAN = {chess.board.PAWN: chess.san.SANConverter.PAWN,
- chess.board.KNIGHT: chess.san.SANConverter.KNIGHT,
- chess.board.BISHOP: chess.san.SANConverter.BISHOP,
- chess.board.ROOK: chess.san.SANConverter.ROOK,
- chess.board.QUEEN: chess.san.SANConverter.QUEEN,
- chess.board.KING: chess.san.SANConverter.KING}
- __sanToType = {}
- for (a, b) in __typeToSAN.iteritems():
- __sanToType[b] = a
-
- def __init__(self, board, moveNumber):
- self.board = board
- self.moveNumber = moveNumber
- chess.san.SANConverter.__init__(self)
-
- def decode(self, colour, move):
- (start, end, result, promotionType) = chess.san.SANConverter.decode(self, self.__colourToSAN[colour], move)
- return (start, end, self.__sanToType[promotionType])
-
- def encode(self, start, end, isTake, promotionType):
- if promotionType is None:
- promotion = self.QUEEN
- else:
- promotion = self.__typeToSAN[promotionType]
- return chess.san.SANConverter.encode(self, start, end, isTake, promotion)
-
- def getPiece(self, location):
- """Called by chess.san.SANConverter"""
- piece = self.board.getPiece(location, self.moveNumber)
- if piece is None:
- return None
- return (self.__colourToSAN[piece.getColour()], self.__typeToSAN[piece.getType()])
-
- def testMove(self, colour, start, end, promotionType, allowSuicide = False):
- """Called by chess.san.SANConverter"""
- move = self.board.testMove(self.__sanToColour[colour], start, end,
- self.__sanToType[promotionType], allowSuicide, self.moveNumber)
- if move is None:
- return False
-
- if move.opponentInCheck:
- if not move.opponentCanMove:
- return chess.san.SANConverter.CHECKMATE
- return chess.san.SANConverter.CHECK
- return True
-
- class ChessGame:
- """
- """
-
- def __init__(self):
- """Game constructor"""
- self.__players = []
- self.__spectators = []
- self.__whitePlayer = None
- self.__blackPlayer = None
- self.__currentPlayer = None
- self.__moves = []
- self.__inCallback = False
- self.__queuedCalls = []
- self.board = ChessGameBoard(self)
-
- self.__started = False
- self.result = RESULT_IN_PROGRESS
- self.rule = None
- self.whiteTimer = None
- self.blackTimer = None
-
- def getAlivePieces(self, moveNumber = -1):
- """Get the alive pieces on the board.
-
- 'moveNumber' is the move to get the pieces from (integer).
-
- Returns a dictionary of the alive pieces (board.ChessPiece) keyed by location.
- Raises an IndexError exception if moveNumber is invalid.
- """
- return self.board.getAlivePieces(moveNumber)
-
- def getDeadPieces(self, moveNumber = -1):
- """Get the dead pieces from the game.
-
- 'moveNumber' is the move to get the pieces from (integer).
-
- Returns a list of the pieces (board.ChessPiece) in the order they were killed.
- Raises an IndexError exception if moveNumber is invalid.
- """
- return self.board.getDeadPieces(moveNumber)
-
- def setTimers(self, whiteTimer, blackTimer):
- """
- """
- self.whiteTimer = whiteTimer
- self.blackTimer = blackTimer
-
- def setWhite(self, player):
- """Set the white player in the game.
-
- 'player' is the player to use as white.
-
- If the game has started or there is a white player an exception is thrown.
- """
- assert(self.__started is False)
- assert(self.__whitePlayer is None)
- self.__whitePlayer = player
- self.__connectPlayer(player)
-
- def getWhite(self):
- """Returns the current white player (player.Player)"""
- return self.__whitePlayer
-
- def setBlack(self, player):
- """Set the black player in the game.
-
- 'player' is the player to use as black.
-
- If the game has started or there is a black player an exception is thrown.
- """
- assert(self.__started is False)
- assert(self.__blackPlayer is None)
- self.__blackPlayer = player
- self.__connectPlayer(player)
-
- def getBlack(self):
- """Returns the current white player (player.Player)"""
- return self.__blackPlayer
-
- def getCurrentPlayer(self):
- """Get the player to move"""
- return self.__currentPlayer
-
- def addSpectator(self, player):
- """Add a spectator to the game.
-
- 'player' is the player spectating.
-
- This can be called after the game has started.
- """
- self.__spectators.append(player)
- self.__connectPlayer(player)
-
- def isStarted(self):
- """Returns True if the game has been started"""
- return self.__started
-
- def start(self, moves = []):
- """Start the game.
-
- 'moves' is a list of moves to start with.
-
- If there is no white or black player then an exception is raised.
- """
- assert(self.__whitePlayer is not None and self.__blackPlayer is not None)
-
- # Disabled for now
- #import network
- #self.x = network.GameReporter('Test Game', 12345)
- #print 'Reporting'
-
- # Load starting moves
- self.__currentPlayer = self.__whitePlayer
- for move in moves:
- self.move(self.__currentPlayer, move)
- if self.__currentPlayer is self.__whitePlayer:
- self.__currentPlayer = self.__blackPlayer
- else:
- self.__currentPlayer = self.__whitePlayer
-
- self.__started = True
-
- # Stop if both players aren't alive
- if not self.__whitePlayer.isAlive:
- self.killPlayer(self.__whitePlayer)
- return
- if not self.__blackPlayer.isAlive:
- self.killPlayer(self.__blackPlayer)
- return
-
- # Stop if game ended on loaded moves
- if self.result != RESULT_IN_PROGRESS:
- self._notifyEndGame()
- return
-
- self.startLock()
-
- # Inform other players of the result
- for player in self.__players:
- player.onPlayerStartTurn(self.__currentPlayer)
-
- # Get the next player to move
- self.__currentPlayer._setReadyToMove(True)
-
- self.endLock()
-
- def getSquareOwner(self, coord):
- """TODO
- """
- piece = self.board.getPiece(coord)
- if piece is None:
- return None
-
- colour = piece.getColour()
- if colour is chess.board.WHITE:
- return self.__whitePlayer
- elif colour is chess.board.BLACK:
- return self.__blackPlayer
- else:
- return None
-
- def canMove(self, player, start, end, promotionType):
- """Test if a player can move.
-
- 'player' is the player making the move.
- 'start' is the location to move from in LAN format (string).
- 'end' is the location to move from in LAN format (string).
- 'promotionType' is the piece type to promote pawns to. FIXME: Make this a property of the player
-
- Return True if can move, otherwise False.
- """
- if player is not self.__currentPlayer:
- return False
-
- if player is self.__whitePlayer:
- colour = chess.board.WHITE
- elif player is self.__blackPlayer:
- colour = chess.board.BLACK
- else:
- assert(False)
-
- move = self.board.testMove(colour, start, end, promotionType = promotionType)
-
- return move is not None
-
- def move(self, player, move):
- """Get a player to make a move.
-
- 'player' is the player making the move.
- 'move' is the move to make in SAN or LAN format (string).
- """
- if self.__inCallback:
- self.__queuedCalls.append((self.move, (player, move)))
- return
-
- self.startLock()
-
- if player is not self.__currentPlayer:
- print 'Player attempted to move out of turn'
- else:
- self._move(player, move)
-
- self.endLock()
-
- def undo(self, player):
- if self.__inCallback:
- self.__queuedCalls.append((self.undo, (player,)))
- return
-
- self.startLock()
-
- self.__whitePlayer._setReadyToMove(False)
- self.__blackPlayer._setReadyToMove(False)
-
- # Pretend the current player is the oponent so when endMove() is called
- # this player will become the active player.
- # Yes, this IS a big hack...
- if player is self.__whitePlayer:
- self.__currentPlayer = self.__blackPlayer
- else:
- self.__currentPlayer = self.__whitePlayer
-
- # If this player hasn't moved then undo oponents move before their one
- if len(self.__moves) > 0 and self.__moves[-1].player is not player:
- count = 2
- else:
- count = 1
-
- for i in xrange(count):
- if len(self.__moves) != 0:
- self.board.undo()
- self.__moves = self.__moves[:-1]
- for p in self.__players:
- p.onUndoMove()
-
- self.endLock()
-
- def startLock(self):
- assert(self.__inCallback is False)
- self.__inCallback = True
-
- def endLock(self):
- self.__inCallback = False
- while len(self.__queuedCalls) > 0:
- (call, args) = self.__queuedCalls[0]
- self.__queuedCalls = self.__queuedCalls[1:]
- call(*args)
-
- def _move(self, player, move):
- """
- """
- if self.result != RESULT_IN_PROGRESS:
- print 'Game completed'
- return
-
- if self.__currentPlayer is self.__whitePlayer:
- colour = chess.board.WHITE
- else:
- colour = chess.board.BLACK
-
- # If move is SAN process it as such
- try:
- (start, end, _, _, promotionType, _) = chess.lan.decode(colour, move)
- except chess.lan.DecodeError, e:
- converter = ChessGameSANConverter(self.board, len(self.__moves))
- try:
- (start, end, promotionType) = converter.decode(colour, move)
- except chess.san.Error, e:
- print 'Invalid move: ' + move
- return
-
- # Only use promotion type if a pawn move to far file
- piece = self.board.getPiece(start)
- promotion = None
- if piece is not None and piece.getType() is chess.board.PAWN:
- if colour is chess.board.WHITE:
- if end[1] == '8':
- promotion = promotionType
- else:
- if end[1] == '1':
- promotion = promotionType
-
- moveResult = self.board.movePiece(colour, start, end, promotionType)
- if moveResult is None:
- print 'Illegal move: ' + str(move)
- return
-
- # Re-encode for storing and reporting
- canMove = chess.lan.encode(colour, start, end, promotionType = promotion)
- converter = ChessGameSANConverter(self.board, len(self.__moves))
- try:
- sanMove = converter.encode(start, end, moveResult.victim != None, promotionType)
- except chess.san.Error:
- # If for some reason we couldn't make the SAN move the use the CAN move instead
- sanMove = canMove
-
- m = ChessMove()
- if len(self.__moves) == 0:
- m.number = 1
- else:
- m.number = self.__moves[-1].number + 1
- m.player = self.__currentPlayer
- m.piece = piece
- m.victim = moveResult.victim
- m.start = start
- m.end = end
- m.canMove = canMove
- m.sanMove = sanMove
- m.opponentInCheck = moveResult.opponentInCheck
- m.opponentCanMove = moveResult.opponentCanMove
- m.fiftyMoveRule = moveResult.fiftyMoveRule
- m.threeFoldRepetition = moveResult.threeFoldRepetition
- #FIXME: m.comment = move.comment
- #FIXME: m.nag = move.nag
-
- self.__moves.append(m)
-
- # This player has now moved
- self.__currentPlayer._setReadyToMove(False)
-
- # Inform other players of the result
- for player in self.__players:
- player.onPlayerMoved(self.__currentPlayer, m)
-
- # Check if the game has ended
- result = RESULT_IN_PROGRESS
- if not m.opponentCanMove:
- if self.__currentPlayer is self.__whitePlayer:
- result = RESULT_WHITE_WINS
- else:
- result = RESULT_BLACK_WINS
- if m.opponentInCheck:
- rule = RULE_CHECKMATE
- else:
- result = RESULT_DRAW
- rule = RULE_STALEMATE
-
- # Check able to complete
- if not self.board.sufficientMaterial():
- result = RESULT_DRAW
- rule = RULE_INSUFFICIENT_MATERIAL
-
- if result is not RESULT_IN_PROGRESS:
- self.endGame(result, rule)
-
- def endMove(self, player):
- """
- """
- if self.__inCallback:
- self.__queuedCalls.append((self.endMove, (player,)))
- return
-
- if player is not self.__currentPlayer:
- print 'Player attempted to move out of turn'
- return
- if player.move is None:
- print "Ending move when haven't made one"
- return
-
- if self.__currentPlayer is self.__whitePlayer:
- self.__currentPlayer = self.__blackPlayer
- else:
- self.__currentPlayer = self.__whitePlayer
-
- self.startLock()
-
- # Inform other players of the result
- for player in self.__players:
- player.onPlayerStartTurn(self.__currentPlayer)
-
- # Notify the next player they can move
- if self.__started is True and self.result == RESULT_IN_PROGRESS:
- self.__currentPlayer._setReadyToMove(True)
-
- self.endLock()
-
- def resign(self, player):
- """Get a player to resign.
-
- 'player' is the player resigning.
- """
- rule = RULE_RESIGN
- if player is self.__whitePlayer:
- self.endGame(RESULT_BLACK_WINS, rule)
- else:
- self.endGame(RESULT_WHITE_WINS, rule)
-
- def claimDraw(self):
- """
- """
- # TODO: Penalise if make an incorrect attempt
- try:
- move = self.__moves[-1]
- except IndexError:
- return False
- else:
- if move.fiftyMoveRule:
- rule = RULE_FIFTY_MOVES
- elif move.threeFoldRepetition:
- rule = RULE_THREE_FOLD_REPETITION
- else:
- return False
-
- self.endGame(RESULT_DRAW, rule)
- return True
-
- def killPlayer(self, player):
- """Report a player has died
-
- 'player' is the player that has died.
- """
- if player is self.__whitePlayer:
- result = RESULT_BLACK_WINS
- elif player is self.__blackPlayer:
- result = RESULT_WHITE_WINS
- self.endGame(result, RULE_DEATH)
-
- def outOfTime(self, player):
- """Report a player's timer has expired"""
- if player is self.__whitePlayer:
- result = RESULT_BLACK_WINS
- elif player is self.__blackPlayer:
- result = RESULT_WHITE_WINS
- else:
- assert(False)
- self.endGame(result, RULE_TIMEOUT)
-
- def abandon(self):
- self.endGame(RESULT_DRAW, RULE_ABANDONMENT)
-
- def endGame(self, result, rule):
- if self.result != RESULT_IN_PROGRESS:
- return
- self.result = result
- self.rule = rule
- if self.isStarted():
- self._notifyEndGame()
-
- def _notifyEndGame(self):
- self.__currentPlayer._setReadyToMove(False)
- for player in self.__players:
- player.onGameEnded(self)
-
- def getMoves(self):
- """
- """
- return self.__moves
-
- def abort(self):
- """End the game"""
- # Inform players
- for player in self.__players:
- player.onGameEnded(self)
-
- # Private methods:
-
- def __connectPlayer(self, player):
- """Add a player into the game.
-
- 'player' is the player to add.
-
- The player will be notified of the current state of the board.
- """
- self.__players.append(player)
- player._setGame(self)
-
- # Notify the player of the current state
- # FIXME: Make the board iteratable...
- for file in '12345678':
- for rank in 'abcdefgh':
- coord = rank + file
- piece = self.board.getPiece(coord)
- if piece is None:
- continue
-
- # These are moves from nowhere to their current location
- player.onPieceMoved(piece, None, coord, False)
-
- def _onPieceMoved(self, piece, start, end, delete):
- """Called by the chess board"""
-
- # Notify all players of creations and deletions
- # NOTE: Normal moves are done above since the SAN moves are calculated before the move...
- # FIXME: Change this so the SAN moves are done afterwards...
- for player in self.__players:
- player.onPieceMoved(piece, start, end, delete)
-
- class NetworkChessGame(ChessGame):
- """
- """
-
- def move(self, player, move):
- """Get a player to make a move.
-
- 'player' is the player making the move.
- 'move' is the move to make. It can be of the form:
- A coordinate move in the form ((file0, rank0), (file1, rank1), promotionType) ((int, int), (int, int), chess.board.PIECE_TYPE) or
- A SAN move (string).
- """
- # Send to the server
-
-
- if __name__ == '__main__':
- game = ChessGame()
-
- import pgn
-
- p = pgn.PGN('black.pgn')
- g = p.getGame(0)
-
- class PGNPlayer(ChessPlayer):
-
- def __init__(self, isWhite):
- self.__isWhite = isWhite
- self.__moveNumber = 1
-
- def readyToMove(self):
- if self.__isWhite:
- move = g.getWhiteMove(self.__moveNumber)
- else:
- move = g.getBlackMove(self.__moveNumber)
- self.__moveNumber += 1
- self.move(move)
-
- white = PGNPlayer(True)
- black = PGNPlayer(False)
-
- game.setWhite(white)
- game.setBlack(black)
-
- game.start()
-